﻿using System;
using System.Reflection;
using System.Linq.Expressions;
using System.Linq;
using System.Runtime.Serialization;

namespace ExpressionSerialization
{
  [KnownType(typeof(XExpressionList))]
  [DataContract]
  public class XMethodCallExpression:XExpression
  {
    [DataMember]
    public XExpression XObject { get; set; }
    [DataMember]
    public string MethodName { get; set; }
    [DataMember]
    public XExpressionList Arguments { get; set; }
    [DataMember] //todo:null nepřenášet
    public object Value { get; set; } // Používám, když měním na konstantu

    public override void FromExpression(Expression source_)
    {
      base.FromExpression(source_);
      MethodCallExpression exp = source_ as MethodCallExpression;
      //-- Object --------
      XObject = XExpression.CreateFromExpression(exp.Object);
      //-- Method -----
      if(exp.Method.DeclaringType == typeof(string)) {
        string serializableName = exp.Method.DeclaringType.ToSerializableForm() + '|';
        if(!exp.Method.IsGenericMethod) {
          serializableName += exp.Method.ToString();
        } else {
          serializableName += exp.Method.GetGenericMethodDefinition().ToString() + '|' +
              String.Join("|", exp.Method.GetGenericArguments().Select(ty => ty.ToSerializableForm()).ToArray());
        }
        MethodName = serializableName;
        //-- Arguments -------
        Arguments = new XExpressionList(exp.Arguments);
      } else {
        // vyhodnocuji lokálně a měním na konstantu
        var ss = System.Linq.Expressions.Expression.Convert(exp, typeof(object));
        Expression<Func<object>> lambda = System.Linq.Expressions.Expression.Lambda<Func<object>>(ss);
        Func<object> fn = lambda.Compile();
        object value = fn();
        // base
        TypeName = ReflectionExtensions.ToSerializableForm(value.GetType());
        NodeType = ExpressionType.Constant;
        Value = value;

      }
    }

    public override Expression ToExpression(Type dataContract_, System.Collections.Generic.List<ParameterExpression> parameters_)
    {
      if(NodeType == ExpressionType.Constant) {
        return System.Linq.Expressions.Expression.Constant(Value);
      } else {
        //-- Method -----
        string[] fullName = MethodName.Split('|');
        string name = fullName[1];
        MethodInfo method = (from m in ReflectionExtensions.TypeFromSerializableForm(fullName[0], dataContract_).GetMethods()
                             where m.ToString() == name
                             select m).First();

        if(method.IsGenericMethod) {
          method = method.MakeGenericMethod(fullName.Skip(2).Select(s => ReflectionExtensions.TypeFromSerializableForm(s, dataContract_)).ToArray());
        }

        return Expression.Call(XObject.ToExpression(dataContract_, parameters_), method, Arguments.ToExpressions(dataContract_, parameters_).ToArray<Expression>());
      }
    }
  }
}
